﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Dotnet.Utils.Utility.ObjectExtensions.Number
{
    /// <summary>
    /// 计算器工具。
    /// </summary>
    /// <remarks>
    /// 通过表达式字符串，解析后运算出结果。
    /// </remarks>
    public class CalculateUtil
    {
        private static string Precede(string p, string q)
        {
            switch (p)
            {
                case "+":
                case "-": return "*/(".IndexOf(q) != -1 ? "<" : ">";
                case "*":
                case "/": return q == "(" ? "<" : ">";
                case "(": return q == ")" ? "=" : "<";
                case ")": return q == "(" ? "?" : ">";
                case "#": return q == "#" ? "=" : "<";
            }
            return "?";
        }
        private static double Operate(double a, char o, double b)
        {
            switch (o)
            {
                case '+': return a + b;
                case '-': return a - b;
                case '*': return a * b;
                case '/': return a / b;
            }
            return 0;
        }
        /// <summary>
        /// 把计算数据代入计算公式运算
        /// </summary>
        /// <param name="sExpression"><see cref="string"/>，计算公式</param>
        /// <param name="express"><see cref="Hashtable"/>，计算数据</param>
        /// <example>
        /// <code>
        /// string sExpression = "A*(B-C)+1";
        /// HashTable data = new HashTable(13);
        /// data.Add("A",2);
        /// data.Add("B",3);
        /// data.Add("C",1);
        /// System.Console.Write("{0}={1}",sExpression,CalculateUtil.Calculate(sExpression,data));
        /// </code>
        /// <B>输出：</B>A*(B-C)+1=5
        /// </example>
        /// <returns><see cref="object"/>，返回运算结果</returns>
        public static object Calculate(string sExpression, Hashtable express)
        {
            foreach (string sKey in express.Keys)
            {
                string sValue = express[sKey].ToString();
                sExpression = sExpression.Replace(sKey, sValue);
            }
            return Calculate(sExpression);
        }
        /// <summary>
        /// 通过计算式运算
        /// </summary>
        /// <param name="sExpression"><see cref="string"/>，输入计算式</param>
        /// <example>
        /// <code>
        /// System.Console.Write("2*(3-1)+1={0}",CalculateUtil.Calculate("2*(3-1)+1"));
        /// </code>
        /// <B>输出：</B>2*(3-1)+1=5
        /// </example>
        /// <returns><see cref="object"/>，返回运算结果</returns>
        public static object Calculate(string sExpression)
        {
            Stack nArr = new Stack(), oArr = new Stack();
            int j = 0;
            double a = 0, b = 0;
            string w = "";
            char o;
            MatchCollection arr = Regex.Matches(sExpression.Replace(" ", "") + "#", @"(((?<=(^|\())-)?\d+(\.\d+)?|\D)");

            oArr.Push('#');
            w = Convert.ToString(arr[j++]);
            while (!(w == "#" && Convert.ToString(oArr.Peek()) == "#"))
            {
                if ("+-*/()#".IndexOf(w) != -1)
                {
                    switch (Precede(oArr.Peek().ToString(), w))
                    {
                        case "<":
                            oArr.Push(w);
                            w = Convert.ToString(arr[j++]);
                            break;
                        case "=":
                            oArr.Pop();
                            w = Convert.ToString(arr[j++]);
                            break;
                        case ">":
                            o = Convert.ToChar(oArr.Pop());
                            b = Convert.ToDouble(nArr.Pop());
                            a = Convert.ToDouble(nArr.Pop());
                            nArr.Push(Operate(a, o, b));
                            break;
                        default:
                            return "Error";
                    }
                }
                else
                {
                    nArr.Push(w);
                    w = Convert.ToString(arr[j++]);
                }
            }
            return nArr.Pop();
        }

    }
}